package com.pap.activitiy.controller.activity;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletResponse;

import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.engine.HistoryService;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.identity.User;
import org.activiti.engine.impl.RepositoryServiceImpl;
import org.activiti.engine.impl.identity.Authentication;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.impl.pvm.PvmTransition;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.DeploymentBuilder;
import org.activiti.engine.repository.Model;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Comment;
import org.activiti.engine.task.Task;
import org.activiti.image.ProcessDiagramGenerator;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.pap.activitiy.config.modeler.util.ActivityStatus;
import com.pap.activitiy.config.modeler.util.ToWeb;
import com.pap.activitiy.dto.task.HistoricActivityInstanceDTO;
import com.pap.activitiy.dto.task.HistoricProcessDTO;
import com.pap.activitiy.dto.task.TaskDTO;
import com.pap.logback.builder.LogbackLoggerBuilder;

/**
 * 整合完成后，启动项目，
 * 访问http://localhost:8080/static/modeler.html?modelId=1我们可以看到一个空白的编辑器，
 * 里面什么也木有。原因是什么呢，是因为我们的modelId=1是乱输入的， 他应该要和数据库表ACT_RE_MODEL对应起来才对。
 * 那么这张表里的数据如何来的呢，我们需要自己写一个Controller，对应封装4个方法： 1.新建一个空的模型； 2.所有模型列表； 3.发布模型；
 * 4.删除模型； （activiti已提供了保存修改和获取模型节点信息的方法， 就是StencilsetRestResource
 * ModelEditorJsonRestResource ModelSaveRestResource）
 * 
 * @author alexgaoyh
 *
 */
@RestController
@RequestMapping("models")
public class ModelerController {
	
	private static Logger logger = LogbackLoggerBuilder.getLogger(ModelerController.class.getName());


	@Autowired
	private ProcessEngine processEngine;
	
	@Autowired
	private RepositoryService repositoryService;
	
	@Autowired
	private ProcessEngineConfiguration processEngineConfiguration;
	
	@Autowired
	private HistoryService historyService;
	
	@Autowired
	private ObjectMapper objectMapper;

	/**
	 * 新建一个空模型
	 * 
	 * @return
	 * @throws UnsupportedEncodingException
	 */
	@RequestMapping("/create/{name}/{description}/{revision}/{key}")
	public Object newModel(@PathVariable("name") String name, @PathVariable("description") String description,
			@PathVariable("revision") Integer revision, @PathVariable("key") String key)
			throws UnsupportedEncodingException {
		RepositoryService repositoryService = processEngine.getRepositoryService();
		// 初始化一个空模型
		Model model = repositoryService.newModel();

		ObjectNode modelNode = objectMapper.createObjectNode();
		modelNode.put(ModelDataJsonConstants.MODEL_NAME, name);
		modelNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
		modelNode.put(ModelDataJsonConstants.MODEL_REVISION, revision);

		model.setName(name);
		model.setKey(key);
		model.setMetaInfo(modelNode.toString());

		repositoryService.saveModel(model);
		String id = model.getId();

		// 完善ModelEditorSource
		ObjectNode editorNode = objectMapper.createObjectNode();
		editorNode.put("id", "canvas");
		editorNode.put("resourceId", "canvas");
		ObjectNode stencilSetNode = objectMapper.createObjectNode();
		stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
		editorNode.put("stencilset", stencilSetNode);
		repositoryService.addModelEditorSource(id, editorNode.toString().getBytes("utf-8"));
		return ToWeb.buildResult().putExtra("modelId", id).redirectUrl("/modeler.html?modelId=" + id);
	}

	/**
	 * 获取所有模型
	 * 
	 * @return
	 */
	@GetMapping
	public Object modelList() {
		RepositoryService repositoryService = processEngine.getRepositoryService();
		List<Model> models = repositoryService.createModelQuery().list();
		return ToWeb.buildResult().putExtra("models", models);
	}

	/**
	 * 删除模型
	 * 
	 * @param id
	 * @return
	 */
	@DeleteMapping("{id}")
	public Object deleteModel(@PathVariable("id") String id) {
		RepositoryService repositoryService = processEngine.getRepositoryService();
		repositoryService.deleteModel(id);
		return ToWeb.buildResult().refresh();
	}

	/**
	 * 发布模型为流程定义
	 * 
	 * @param id
	 * @return
	 * @throws Exception
	 */
	@PostMapping("{id}/deployment")
	public Object deploy(@PathVariable("id") String id) throws Exception {

		// 获取模型
		RepositoryService repositoryService = processEngine.getRepositoryService();
		Model modelData = repositoryService.getModel(id);
		byte[] bytes = repositoryService.getModelEditorSource(modelData.getId());

		if (bytes == null) {
			return ToWeb.buildResult().status(ActivityStatus.FAIL).msg("模型数据为空，请先设计流程并成功保存，再进行发布。");
		}

		JsonNode modelNode = new ObjectMapper().readTree(bytes);

		BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode);
		if (model.getProcesses().size() == 0) {
			return ToWeb.buildResult().status(ActivityStatus.FAIL).msg("数据模型不符要求，请至少设计一条主线流程。");
		}
		byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(model);

		// 发布流程
		String processName = modelData.getName() + ".bpmn20.xml";
		DeploymentBuilder deploymentBuilder = repositoryService.createDeployment();
		deploymentBuilder.name(processName);
		deploymentBuilder.addString(processName, new String(bpmnBytes, "UTF-8"));
		Deployment deployment = deploymentBuilder.deploy();
		modelData.setDeploymentId(deployment.getId());
		modelData.setName(modelData.getName());
		modelData.setCategory(modelData.getCategory());
		modelData.setTenantId(modelData.getTenantId());
		repositoryService.saveModel(modelData);

		return ToWeb.buildResult().refresh();
	}

	/***
	 * 	查询组任务列表	查询当前人的组任务
	 * 	可以理解为   我的代办
	 * 
	 * 	在线Web流程模型编辑器维护过程中，选中添加的UserTask 节点，操作面板中选中 Assignment，
	 * 弹出框中可以指定Candidate groups， 这里的角色资源注意添加为act_id_group.id ，而不能是角色中文描述
	 * 	这样，就可以获取到当前用户下的任务
	 * @param startUserId
	 * @param response
	 */
	@RequestMapping("/findGroupTaskListByUserId/{startUserId}")
	public List<TaskDTO> findGroupList(@PathVariable("startUserId") String startUserId, HttpServletResponse response){
        List<Task> list = processEngine.getTaskService()//
                        .createTaskQuery()//
                        .taskCandidateUser(startUserId)//指定组任务查询	组任务的办理人查询
                        /** 排序 */
                        .orderByTaskCreateTime().asc()// 使用创建时间的升序排列
                        .list();
        
        List<TaskDTO> taskDTOList = new ArrayList<TaskDTO>();
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        for(Task task:list ){
        	TaskDTO taskDTO = new TaskDTO();
        	taskDTO.setId(task.getId());
        	taskDTO.setName(task.getName());
        	taskDTO.setCreateTime(formatter.format(task.getCreateTime()));
        	taskDTO.setAssignee(task.getAssignee());
        	taskDTO.setProcessInstanceId(task.getProcessInstanceId());
        	taskDTO.setProcessDefinitionId(task.getProcessDefinitionId());
        	taskDTO.setExecutionId(task.getExecutionId());
        	taskDTOList.add(taskDTO);
        }
        return taskDTOList;
    }
	
	/***
	 * 	查询组任务列表	查询当前人的个人任务
	 * 	可以理解为   我的代办
	 * 
	 * @param startUserId
	 * @param response
	 */
	@RequestMapping("/findUserTaskListByUserId/{startUserId}")
	public List<TaskDTO> findUserTaskList(@PathVariable("startUserId") String startUserId, HttpServletResponse response){
        List<Task> list = processEngine.getTaskService()//
                        .createTaskQuery()//
                        .taskAssignee(startUserId)
                        /** 排序 */
                        .orderByTaskCreateTime().asc()// 使用创建时间的升序排列
                        .list();
        
        List<TaskDTO> taskDTOList = new ArrayList<TaskDTO>();
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        for(Task task:list ){
        	TaskDTO taskDTO = new TaskDTO();
        	taskDTO.setId(task.getId());
        	taskDTO.setName(task.getName());
        	taskDTO.setCreateTime(formatter.format(task.getCreateTime()));
        	taskDTO.setAssignee(task.getAssignee());
        	taskDTO.setProcessInstanceId(task.getProcessInstanceId());
        	taskDTO.setProcessDefinitionId(task.getProcessDefinitionId());
        	taskDTO.setExecutionId(task.getExecutionId());
        	taskDTOList.add(taskDTO);
        }
        return taskDTOList;
    }
	
	/**
	 * 认领任务
	 * 通常一个任务为公共任务任务都有一个以上的候选者，用户想要办理它应该先进行认领任务操作，即把自己变成任务的拥有者。
	 * @param userId
	 * @param taskId
	 * @param response
	 * @return
	 */
	@RequestMapping("/claimRisk/{userId}/{taskId}")
	public String claimRisk(@PathVariable("userId") String userId, 
			@PathVariable("taskId") String taskId, HttpServletResponse response){
		// 让指定userId的用户认领指定taskId的任务
		processEngine.getTaskService().claim(taskId, userId);
		System.out.println("认领操作完毕！！");
		return "SUCCESS";
    }
	
	/**
	 * 根据 processInstanceId， 查询任务关联的BusinessKey（业务系统单据信息）
	 * @param processInstanceId	流程实例ID
	 * @param response
	 * @return
	 */
	@RequestMapping("/selectBusinessKeyByProcessInstanceId/{processInstanceId}")
	public String selectBusinessKeyByProcessInstanceId(@PathVariable("processInstanceId") String processInstanceId, HttpServletResponse response){
		ProcessInstance processInstance = processEngine.getRuntimeService().createProcessInstanceQuery().processInstanceId(processInstanceId).active().singleResult();
        String businessKey = processInstance.getBusinessKey();
        return businessKey;
	}
	
	/**
	 * 任务审批 - 通过
	 * @param userId	当前操作人
	 * @param taskId	任务编号
	 * @param comment	备注
	 * @param response
	 * @return
	 */
	@RequestMapping("/taskOperationPaas/{userId}/{taskId}/{comment}")
	public String taskOperationPaas(@PathVariable("userId") String userId, @PathVariable("taskId") String taskId, 
			@PathVariable("comment") String comment, HttpServletResponse response){
		// 由于流程用户上下文对象是线程独立的，所以要在需要的位置设置，要保证设置和获取操作在同一个线程中
        Authentication.setAuthenticatedUserId(userId);//批注人的名称  一定要写，不然查看的时候不知道人物信息
        // 添加批注信息
        processEngine.getTaskService().addComment(taskId, null, comment);//comment为批注内容
        // 审批人
        processEngine.getTaskService().setAssignee(taskId, userId);
        // 完成任务
        Map<String, Object> variables=new HashMap<String, Object>();
        variables.put("flag", true);
        processEngine.getTaskService().complete(taskId, variables);//vars是一些变量
        return "SUCCESS";
	}
	
	/**
	 * 任务审批 - 拒绝
	 * @param userId	当前操作人
	 * @param taskId	任务编号
	 * @param comment	备注
	 * @param response
	 * @return
	 */
	@RequestMapping("/taskOperationReject/{userId}/{taskId}/{comment}")
	public String taskOperationReject(@PathVariable("userId") String userId, @PathVariable("taskId") String taskId, 
			@PathVariable("comment") String comment, HttpServletResponse response){
		// 由于流程用户上下文对象是线程独立的，所以要在需要的位置设置，要保证设置和获取操作在同一个线程中
        Authentication.setAuthenticatedUserId(userId);//批注人的名称  一定要写，不然查看的时候不知道人物信息
        // 添加批注信息
        processEngine.getTaskService().addComment(taskId, null, comment);//comment为批注内容
        // 审批人
        processEngine.getTaskService().setAssignee(taskId, userId);
        // 完成任务
        Map<String, Object> variables=new HashMap<String, Object>();
        variables.put("flag", false);
        processEngine.getTaskService().complete(taskId, variables);//vars是一些变量
        return "SUCCESS";
	}
	
	/**
	 * 我的申请
	 * @param userId
	 * @param taskId
	 * @param comment
	 * @param response
	 * @return
	 */
	@RequestMapping("/myTaskByUserId/{userId}")
	public List<HistoricProcessDTO> myTaskByUserId(@PathVariable("userId") String userId, HttpServletResponse response){
		List<HistoricProcessInstance> historicProcessInstanceList = historyService.createHistoricProcessInstanceQuery().startedBy(userId).list();
		
		List<HistoricProcessDTO> historicProcessDTOList = new ArrayList<HistoricProcessDTO>();
		for (HistoricProcessInstance historicProcessInstance : historicProcessInstanceList) {
			HistoricProcessDTO historicProcessDTO = new HistoricProcessDTO();
			historicProcessDTO.setBusinessKey(historicProcessInstance.getBusinessKey());
			historicProcessDTO.setDurationInMillis(historicProcessInstance.getDurationInMillis());
			historicProcessDTO.setProcessDefinitionName(historicProcessInstance.getProcessDefinitionName());
			historicProcessDTO.setProcessInstanceId(historicProcessInstance.getId());
			historicProcessDTO.setDeleteReason(historicProcessInstance.getDeleteReason());
			historicProcessDTOList.add(historicProcessDTO);
		}
        return historicProcessDTOList;
	}
	
	/**
	 * 删除流程实例
	 * 	被删除的流程信息保存在 act_hi_procinst 历史流程实例表(delete_reason 字段处理)
	 * @param processInstanceId		流程实例
	 * @param deleteReason			删除原因
	 * @param response
	 * @return
	 */
	@RequestMapping("/deleteTaskByProcessInstanceId/{processInstanceId}/{deleteReason}")
	public String deleteTaskByProcessInstanceId(@PathVariable("processInstanceId") String processInstanceId, 
			@PathVariable("deleteReason") String deleteReason, 
			HttpServletResponse response){
		//删除流程
		processEngine.getRuntimeService().deleteProcessInstance(processInstanceId,deleteReason);
        return "SUCCESS";
	}
	
	
	/**
	 * 我的申请，根据processInstanceId， 查询历史的批注历史信息记录
	 * 	形成一个 活动名称、开始时间、截止时间、批注、审批人 的记录信息
	 * @param processInstanceId		拿着 myTaskByUserId 方法的返回值 processInstanceId 进行查询
	 * @param response
	 * @return
	 */
	@RequestMapping("/myHistoryActInstanceByProcessInstanceId/{processInstanceId}")
	public List<HistoricActivityInstanceDTO> myHistoryActInstanceByProcessInstanceId(@PathVariable("processInstanceId") String processInstanceId, 
			HttpServletResponse response){
		// 增加排序功能，方便进行数据查看
		List<HistoricActivityInstance> historicActivityInstanceList = processEngine.getHistoryService().createHistoricActivityInstanceQuery() // 创建历史活动实例查询
	                .processInstanceId(processInstanceId).finished().orderByHistoricActivityInstanceStartTime().desc().list();
		
		SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		
		List<HistoricActivityInstanceDTO> historicActivityInstanceDTOList = new ArrayList<HistoricActivityInstanceDTO>();
		for (HistoricActivityInstance historicActivityInstance : historicActivityInstanceList) {
			HistoricActivityInstanceDTO historicActivityInstanceDTO = new HistoricActivityInstanceDTO();
			historicActivityInstanceDTO.setId(historicActivityInstance.getId());
			historicActivityInstanceDTO.setActivityName(historicActivityInstance.getActivityName());
			historicActivityInstanceDTO.setAssignee(historicActivityInstance.getAssignee());
			historicActivityInstanceDTO.setStartTime(formatter.format(historicActivityInstance.getStartTime()));
			historicActivityInstanceDTO.setEndTime(formatter.format(historicActivityInstance.getEndTime()));
			historicActivityInstanceDTO.setProcessInstanceId(historicActivityInstance.getProcessInstanceId());
			
			if(historicActivityInstance.getAssignee() != null) {
				User user = processEngine.getIdentityService().createUserQuery()
						.userId(historicActivityInstance.getAssignee()).orderByUserId().asc().singleResult();
				if(user != null) {
					historicActivityInstanceDTO.setAssigneeName(user.getFirstName());
				}
			}
			
			if(historicActivityInstance.getTaskId() != null) {
				List<Comment> commentList = processEngine.getTaskService().getTaskComments(historicActivityInstance.getTaskId(), "comment");
				if(commentList != null && commentList.size() > 0) {
					historicActivityInstanceDTO.setComment(commentList.get(0).getFullMessage());
				}
			}
			
			historicActivityInstanceDTOList.add(historicActivityInstanceDTO);
		}
        return historicActivityInstanceDTOList;
	}
	
	/**
	 * 获取流程图像，已执行节点和流程线高亮显示
	 */
	@RequestMapping("/imgshow/{pProcessInstanceId}")
	public void getActivitiProccessImage(@PathVariable(value = "pProcessInstanceId") String pProcessInstanceId, HttpServletResponse response) throws Exception {
		logger.info("[开始]-获取流程图图像");
		// 设置页面不缓存
		response.setHeader("Pragma", "No-cache");
		response.setHeader("Cache-Control", "no-cache");
		response.setDateHeader("Expires", 0);
		try {
			// 获取历史流程实例
			HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery()
					.processInstanceId(pProcessInstanceId).singleResult();

			if (historicProcessInstance == null) {
				throw new Exception();
			} else {
				// 获取流程定义
				ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService)
						.getDeployedProcessDefinition(historicProcessInstance.getProcessDefinitionId());

				// 获取流程历史中已执行节点，并按照节点在流程中执行先后顺序排序
				List<HistoricActivityInstance> historicActivityInstanceList = historyService
						.createHistoricActivityInstanceQuery().processInstanceId(pProcessInstanceId)
						.orderByHistoricActivityInstanceId().asc().list();

				// 已执行的节点ID集合
				List<String> executedActivityIdList = new ArrayList<String>();
				@SuppressWarnings("unused")
				int index = 1;
				logger.info("获取已经执行的节点ID");
				for (HistoricActivityInstance activityInstance : historicActivityInstanceList) {
					executedActivityIdList.add(activityInstance.getActivityId());
					logger.info("第[" + index + "]个已执行节点=" + activityInstance.getActivityId() + " : "
							+ activityInstance.getActivityName());
					index++;
				}
				List<HistoricActivityInstance> highLightedActivitList = historyService
						.createHistoricActivityInstanceQuery().processInstanceId(pProcessInstanceId).list();
				// 高亮线路id集合
				List<String> highLightedFlows = getHighLightedFlows(processDefinition, highLightedActivitList);
				// 获取流程图
				BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());

				// 获取流程图图像字符流
				ProcessDiagramGenerator diagramGenerator = processEngineConfiguration.getProcessDiagramGenerator();
				InputStream imageStream = diagramGenerator.generateDiagram(bpmnModel, "png", executedActivityIdList,
						highLightedFlows,"宋体", "宋体", "宋体", null, 1.0);

				response.setContentType("image/png");
				OutputStream os = response.getOutputStream();
				int bytesRead = 0;
				byte[] buffer = new byte[8192];
				while ((bytesRead = imageStream.read(buffer, 0, 8192)) != -1) {
					os.write(buffer, 0, bytesRead);
				}
				os.close();
				imageStream.close();
			}
			logger.info("[完成]-获取流程图图像");
		} catch (Exception e) {
			logger.error("【异常】-获取流程图失败！" + e.getMessage());
			throw new Exception(e);
		}
	}

	/**
	 * 获取需要高亮的线
	 * 
	 * @param processDefinitionEntity
	 * @param historicActivityInstances
	 * @return
	 */
	private List<String> getHighLightedFlows(ProcessDefinitionEntity processDefinitionEntity,
			List<HistoricActivityInstance> historicActivityInstances) {
		List<String> highFlows = new ArrayList<String>();// 用以保存高亮的线flowId
		for (int i = 0; i < historicActivityInstances.size() - 1; i++) {// 对历史流程节点进行遍历
			ActivityImpl activityImpl = processDefinitionEntity
					.findActivity(historicActivityInstances.get(i).getActivityId());// 得到节点定义的详细信息
			List<ActivityImpl> sameStartTimeNodes = new ArrayList<ActivityImpl>();// 用以保存后需开始时间相同的节点
			ActivityImpl sameActivityImpl1 = processDefinitionEntity
					.findActivity(historicActivityInstances.get(i + 1).getActivityId());
			// 将后面第一个节点放在时间相同节点的集合里
			sameStartTimeNodes.add(sameActivityImpl1);
			for (int j = i + 1; j < historicActivityInstances.size() - 1; j++) {
				HistoricActivityInstance activityImpl1 = historicActivityInstances.get(j);// 后续第一个节点
				HistoricActivityInstance activityImpl2 = historicActivityInstances.get(j + 1);// 后续第二个节点
				if (activityImpl1.getStartTime().equals(activityImpl2.getStartTime())) {
					// 如果第一个节点和第二个节点开始时间相同保存
					ActivityImpl sameActivityImpl2 = processDefinitionEntity
							.findActivity(activityImpl2.getActivityId());
					sameStartTimeNodes.add(sameActivityImpl2);
				} else {
					// 有不相同跳出循环
					break;
				}
			}
			List<PvmTransition> pvmTransitions = activityImpl.getOutgoingTransitions();// 取出节点的所有出去的线
			for (PvmTransition pvmTransition : pvmTransitions) {
				// 对所有的线进行遍历
				ActivityImpl pvmActivityImpl = (ActivityImpl) pvmTransition.getDestination();
				// 如果取出的线的目标节点存在时间相同的节点里，保存该线的id，进行高亮显示
				if (sameStartTimeNodes.contains(pvmActivityImpl)) {
					highFlows.add(pvmTransition.getId());
				}
			}
		}
		return highFlows;
	}

}
